﻿using System;

namespace Quail
{
    /// <summary>
    /// The FCKeditor/TinyMCE control wrapper
    /// </summary>
    public class WebInputHtml : WebInput
    {
        private IInputHtmlImplementor _editor;

        /// <summary>
        /// Initializes a new instance of the <see cref="WebInputHtml"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="selector">The jQuery selector.</param>
        /// <param name="editorType">The HTML editor type</param>
        public WebInputHtml(WebControlContainer container, string selector, InputHtmlControlTypes editorType)
            : base(container, selector)
        {
            InputHtmlControlType = editorType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebInputHtml"/> class.
        /// </summary>
        /// <param name="control">The control. </param>
        /// <param name="editorType">The HTML editor type</param>
        public WebInputHtml(WebControl control, InputHtmlControlTypes editorType)
            : base(control)
        {
            InputHtmlControlType = editorType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebInputHtml"/> class.
        /// </summary>
        public WebInputHtml()
        {
        }

        /// <summary>
        /// The types of html editors supported
        /// </summary>
        public enum InputHtmlControlTypes
        {
            /// <summary>
            /// FCK Editor
            /// </summary>
            FckEditor,

            /// <summary>
            /// TinyMCE Editor
            /// </summary>
            TinyMceEditor,
        }

        /// <summary>
        /// Wraps calls to the Html editor
        /// </summary>
        public interface IInputHtmlImplementor
        {
            /// <summary>
            /// Gets the editor type
            /// </summary>
            InputHtmlControlTypes EditorType { get; }

            /// <summary>
            /// Gets or sets the HTML value of the control
            /// </summary>
            string Value { get; set; }

            /// <summary>
            /// Gets the plain-text value of the control
            /// </summary>
            string PlainText { get; }
        }

        /// <summary>
        /// Gets or sets the HTML editor type
        /// </summary>
        public InputHtmlControlTypes InputHtmlControlType
        {
            get
            {
                return _editor.EditorType;
            }

            set
            {
                SetEditor(value);
            }
        }

        /// <summary>
        /// Gets or sets the value of the editor
        /// </summary>
        public override string Value
        {
            get
            {
                return _editor.Value;
            }

            set
            {
                var task = BeginTask("Set Value");
                task.AddAttribute("Value", value);
                try
                {
                    _editor.Value = value;
                    task.Finish();
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                } 
            }
        }

        private void SetEditor(InputHtmlControlTypes htmlControlType)
        {
            switch (htmlControlType)
            {
                case InputHtmlControlTypes.FckEditor:
                    _editor = new FckEditor(this);
                    break;
                case InputHtmlControlTypes.TinyMceEditor:
                    _editor = new TinyMceEditor(this);
                    break;
                default:
                    throw new WebControlException(this, "Invalid InputHtmlControlTypes passed: " + htmlControlType);
            }
        }

        /// <summary>
        /// Wraps calls to the FckEditor
        /// </summary>
        private class FckEditor : IInputHtmlImplementor
        {
            private readonly WebInputHtml _control;
            private string _instanceName;
            private bool _waited;

            /// <summary>
            /// Initializes a new instance of the <see cref="FckEditor"/> class.
            /// </summary>
            /// <param name="container">The container.</param>
            public FckEditor(WebInputHtml container)
            {
                _control = container;
            }

            /// <summary>
            /// Gets the editor type handled by this implementor (FCK)
            /// </summary>
            public InputHtmlControlTypes EditorType
            {
                get
                {
                    return InputHtmlControlTypes.FckEditor;
                }
            }

            /// <summary>
            /// Gets or sets the value of the editor
            /// </summary>
            public string Value
            {
                get
                {
                    return GetFckEditorObjProp("EditorDocument.body.innerHTML");
                }

                set
                {
                    GetFckEditorObjProp("EditorDocument.body.innerHTML = \"" + value.Replace("\"", "\\\"") + "\"");
                }
            }

            /// <summary>
            /// Gets the value of the html editor as plain text (I don't think this code actually works)
            /// </summary>
            public string PlainText
            {
                get
                {
                    return GetFckEditorObjProp("var oDOM = oEditor.EditorDocument, f = function() {var r=oDOM.createRange();r.selectNodeContents(oDOM.body);return r.toString();}\n oDOM.all ? oDOM.body.innerText : f()");
                }
            }

            /// <summary>
            /// Gets the FCKeditor instance name
            /// </summary>
            private string InstanceName
            {
                get
                {
                    if (!string.IsNullOrEmpty(_instanceName))
                        return _instanceName;

                    try
                    {
                        var src = _control.Src;
                        var idx = src.IndexOf('?');
                        foreach (var q in src.Substring(idx + 1).Split('&'))
                        {
                            var qi = q.Split('=');
                            if (qi.GetUpperBound(0) >= 1 && string.Compare(qi[0], "InstanceName", true) == 0)
                            {
                                _instanceName = qi[1];
                                return _instanceName;
                            }
                        }

                        return string.Empty;
                    }
                    catch (Exception e)
                    {
                        throw new WebControlException(_control, string.Format("Exception getting InstanceName for Locator {0}: {1}", _control.Selector, e.ToString()));
                    }
                }
            }

            /// <summary>
            /// Gets a Fck editor property value
            /// </summary>
            /// <param name="prop">name of property to get</param>
            /// <returns>the value of the property</returns>
            private string GetFckEditorObjProp(string prop)
            {
                try
                {
                    WaitForFckEditor();
                    var eval = "window.FCKeditorAPI.GetInstance(\"" + InstanceName + "\")." + prop;
                    var result = _control.Browser.GetEval(eval);

                    return result;
                }
                catch (Exception e)
                {
                    throw new WebControlException(_control, string.Format("Error with FckEditor {0} property {1}: {2}", InstanceName, prop, e.ToString()));
                }
            }

            /// <summary>
            /// Waits for the editor interface to be available
            /// </summary>
            private void WaitForFckEditor()
            {
                if (_waited)
                    return;

                var task = _control.ActiveTask.BeginTask("WaitForFckEditor");
                try
                {
                    var timeout = _control.Browser.Timeout.ToString();
                    _control.Page.Browser.SeleniumInterface.WaitForCondition("selenium.browserbot.getCurrentWindow(true).FCKeditorAPI != undefined", timeout);
                    _control.Browser.Sleep(5000);
                    task.Rollback();
                    _waited = true;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Wraps calls to the TinyMceEditor
        /// </summary>
        private class TinyMceEditor : IInputHtmlImplementor
        {
            private readonly WebInputHtml _control;
            private bool _waited;

            /// <summary>
            /// Initializes a new instance of the <see cref="TinyMceEditor"/> class.
            /// </summary>
            /// <param name="container">The container.</param>
            public TinyMceEditor(WebInputHtml container)
            {
                _control = container;
            }

            /// <summary>
            /// Gets the editor type handled by this implementor (FCK)
            /// </summary>
            public InputHtmlControlTypes EditorType
            {
                get
                {
                    return InputHtmlControlTypes.TinyMceEditor;
                }
            }

            /// <summary>
            /// Gets or sets the value of the editor
            /// </summary>
            public string Value
            {
                get
                {
                    return GetTinyMceEditorObjProp("getContent()");
                }

                set
                {
                    GetTinyMceEditorObjProp("setContent(\"" + value.Replace("\"", "\\\"") + "\")");
                }
            }

            /// <summary>
            /// Gets the value of the html editor as plain text (I don't think this code actually works)
            /// </summary>
            public string PlainText
            {
                get
                {
                    return GetTinyMceEditorObjProp("var oDOM = oEditor.EditorDocument, f = function() {var r=oDOM.createRange();r.selectNodeContents(oDOM.body);return r.toString();}\n oDOM.all ? oDOM.body.innerText : f()");
                }
            }

            /// <summary>
            /// Gets a Fck editor property value
            /// </summary>
            /// <param name="prop">name of property to get</param>
            /// <returns>the value of the property</returns>
            private string GetTinyMceEditorObjProp(string prop)
            {
                try
                {
                    WaitForTinyMceEditor();
                    var eval = "window.tinyMCE.get(\"" + _control.ID + "\")." + prop;
                    var result = _control.Browser.GetEval(eval);

                    return result;
                }
                catch (Exception e)
                {
                    throw new WebControlException(_control, string.Format("Error with TinyMceEditor {0} property {1}: {2}", _control.Selector, prop, e.ToString()));
                }
            }

            /// <summary>
            /// Waits for the editor interface to be available
            /// </summary>
            private void WaitForTinyMceEditor()
            {
                if (_waited)
                    return;

                var task = _control.ActiveTask.BeginTask("WaitForTinyMceEditor");
                try
                {
                    var timeout = _control.Browser.Timeout.ToString();

                    _control.Page.Browser.SeleniumInterface.WaitForCondition("selenium.browserbot.getCurrentWindow(true).tinyMCE != undefined", timeout);

                    // _control.Browser.Sleep(5000);
                    task.Rollback();
                    _waited = true;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }
    }
}
